﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using UtilZ.Dotnet.Ex.Base;
using UtilZ.Dotnet.Ex.Communication.Base;
using UtilZ.Dotnet.Ex.Exceptions;
using UtilZ.Dotnet.Ex.Model;
using UtilZ.Dotnet.Ex.ZLog;

namespace UtilZ.Dotnet.Ex.Communication.Net.Base
{
    /// <summary>
    /// Udp通信类
    /// </summary>
    public class NetUdpClient : ClientAbs
    {
        private readonly static ITimestampFactory _sendDataIntervalTimestampFactory;
        static NetUdpClient()
        {
            _sendDataIntervalTimestampFactory = new TicksTimestampFactory(new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Local), TimestampUnit.Nanosecond100);
        }


        /// <summary>
        /// 接收Socket
        /// </summary>
        private readonly Socket _client;
        /// <summary>
        /// 接收Socket
        /// </summary>
        public Socket Client
        {
            get { return this._client; }
        }

        private readonly byte[] _buffer;
        private readonly BinaryReader _reader;
        private readonly UdpUnpackerPara _unpackerPara;
        private SocketAsyncEventArgs _recvEventArg = null;

        private bool _status = false;
        /// <summary>
        /// true:运行;false:停止
        /// </summary>
        public bool Status
        {
            get { return _status; }
        }

        /// <summary>
        /// 接收线程
        /// </summary>
        private readonly ThreadEx _receiveThread = null;

        private readonly object _sendIntervalLock = new object();
        private readonly long _sendInterval100NS = 0;
        private long _lastSendDataTimestamp100NS = 0;
        private readonly CancellationTokenEx _sendDataIntervalToken = null;


        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="initPara">Udp客户端初始化参数</param>
        /// <exception cref="ArgumentNullException">dataOutput或receiveEndPoint为null将抛出异常</exception>
        public NetUdpClient(UdpClientInitPara initPara)
            : base(initPara)
        {
            var client = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
            client.ReceiveBufferSize = initPara.ReceiveDataSize;
            client.SendBufferSize = initPara.SendBufferSize;
            client.EnableBroadcast = initPara.EnableBroadcast;
            //client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, 1);
            client.Bind(initPara.LocalEndPoint);

            //如果没有这两行代码,则windows底层BUG会在接收抛出异常,并将socket重置,然后再也收不到数据
            const int SIO_UDP_CONNRESET = -1744830452;
            client.IOControl(SIO_UDP_CONNRESET, new byte[] { 0, 0, 0, 0 }, null);

            if (initPara.SendMillisecondsTimeout > 0)
            {
                client.SendTimeout = initPara.SendMillisecondsTimeout;
            }

            if (initPara.ReceiveMillisecondsTimeout > 0)
            {
                client.ReceiveTimeout = initPara.ReceiveMillisecondsTimeout;
            }

            this._client = client;

            this._buffer = new byte[initPara.ReceiveDataSize];
            this._reader = new BinaryReader(new MemoryStream(this._buffer));
            var netLinkInfo = new NetLinkInfo(null, this._client.LocalEndPoint);
            this._unpackerPara = new UdpUnpackerPara()
            {
                Client = this,
                LinkInfo = netLinkInfo,
                Buffer = this._buffer,
                Offset = 0,
                Reader = this._reader
            };

            switch (initPara.ReceiveDataMode)
            {
                case ThreadMode.Sync:
                    string receiveThreadName = initPara.ReceiveThreadName;
                    if (string.IsNullOrWhiteSpace(receiveThreadName))
                    {
                        receiveThreadName = $"udp接收数据线程{NetHelper.GetIPPortString(initPara.LocalEndPoint)}";
                    }
                    this._receiveThread = new ThreadEx(this.ReceiveDataThreadMethod, receiveThreadName, true);
                    break;
                case ThreadMode.Async:
                    break;
                case ThreadMode.None:
                default:
                    //不接收数据
                    break;
            }

            this._sendInterval100NS = initPara.SendInterval100NS;
            if (this._sendInterval100NS > 0)
            {
                this._sendDataIntervalToken = new CancellationTokenEx();
            }
        }


        /// <summary>
        /// tcp客户端开始接受tcp服务器发送的数据
        /// </summary>
        private void StartRecv(SocketAsyncEventArgs args)
        {
            if (this._disposed)
            {
                return;
            }

            //bool willRaiseEvent = args.AcceptSocket.ReceiveAsync(args);
            var socket = args.AcceptSocket;
            if (socket == null)
            {
                return;
            }

            bool willRaiseEvent = socket.ReceiveFromAsync(args);
            //bool willRaiseEvent = args.AcceptSocket.ReceiveMessageFromAsync(args);
            if (!willRaiseEvent)
            {
                this.ProcessReceive(args);
            }
        }


        private void RecvEventArgs_Completed(object sender, SocketAsyncEventArgs e)
        {
            try
            {
                switch (e.LastOperation)
                {
                    case SocketAsyncOperation.ReceiveFrom:
                        this.ProcessReceive(e);
                        break;
                    case SocketAsyncOperation.Send:
                        //ProcessSend(e);
                        break;
                    default:
                        throw new ArgumentException("The last operation completed on the socket was not a receive or send");
                }
            }
            catch (Exception ex)
            {
                ZLoger.Error(ex);
            }
        }


        /// <summary>
        /// 处理接受到的tcp服务器数据
        /// </summary>
        /// <param name="e"></param>
        private void ProcessReceive(SocketAsyncEventArgs e)
        {
            try
            {
                if (!this._status)
                {
                    return;
                }

                if (e.SocketError == SocketError.Success)
                {
                    if (e.BytesTransferred > 0)
                    {
                        this.OutputReceiveDataBuffer(e.RemoteEndPoint, e.BytesTransferred);
                    }
                }
                else
                {
                    ZLoger.Error(e.ConnectByNameError, $"socket udp接收数据错误，{e.SocketError.ToString()}");
                }

                this.StartRecv(e);
            }
            catch (Exception ex)
            {
                ZLoger.Error(ex);
            }
        }

        private void OutputReceiveDataBuffer(EndPoint remoteEP, int revLen)
        {
            UdpUnpackerPara unpackerPara = this._unpackerPara;
            unpackerPara.DataLength = revLen;
            unpackerPara.Offset = 0;
            unpackerPara.Reader.BaseStream.Position = 0;
            ((NetLinkInfo)unpackerPara.LinkInfo).RemoteEndPoint = remoteEP;
            base._initPara.Unpacker.UnPackge(unpackerPara);
        }


        /// <summary>
        /// udp接收数据线程方法
        /// </summary>
        /// <param name="threadPara">线程参数</param>
        private void ReceiveDataThreadMethod(ThreadExPara threadPara)
        {
            try
            {
                ZLoger.Info($"\"{Thread.CurrentThread.Name}\"已启动...");
                this._status = true;
                EndPoint remoteEP = new IPEndPoint(1, 1);
                int revLen;

                while (this._status)
                {
                    try
                    {
                        if (base._disposed || threadPara.Token.IsCancellationRequested)
                        {
                            break;
                        }

                        //revLen = this._client.ReceiveFrom(buffer, offset, size, SocketFlags.None, ref remoteEP);//一个：使用此重载方法接收数据会存在数据被截断bug
                        revLen = this._client.ReceiveFrom(this._buffer, ref remoteEP);
                        if (revLen <= 0)
                        {
                            continue;
                        }

                        this.OutputReceiveDataBuffer(remoteEP, revLen);
                    }
                    catch (ThreadAbortException)
                    {
                        break;
                    }
                    catch (ObjectDisposedException)
                    {
                        break;
                    }
                    catch (SocketException sex)
                    {
                        if (!NetHelper.SocketErrorIsDisconnect(sex.SocketErrorCode))
                        {
                            ZLoger.Error(sex, "udp接收数据发生异常");
                        }
                    }
                    catch (Exception exi)
                    {
                        ZLoger.Error(exi, "udp接收数据发生异常");
                    }
                }

                ZLoger.Info($"\"{Thread.CurrentThread.Name}\"已停止...");
            }
            catch (Exception ex)
            {
                ZLoger.Error(ex);
            }
        }



        /// <summary>
        /// 开始接收数据
        /// </summary>
        public void Start()
        {
            var initPara = (UdpClientInitPara)base._initPara;
            switch (initPara.ReceiveDataMode)
            {
                case ThreadMode.Sync:
                    if (this._receiveThread != null)
                    {
                        this._receiveThread.Start();
                    }
                    break;
                case ThreadMode.Async:
                    if (this._recvEventArg != null)
                    {
                        this._recvEventArg.AcceptSocket = null;
                        this._recvEventArg.Dispose();
                        this._recvEventArg = null;
                    }

                    var recvEventArgs = new SocketAsyncEventArgs();
                    recvEventArgs.AcceptSocket = this._client;
                    recvEventArgs.RemoteEndPoint = new IPEndPoint(IPAddress.Any, 0);
                    recvEventArgs.SetBuffer(this._buffer, 0, this._buffer.Length);
                    recvEventArgs.Completed += RecvEventArgs_Completed;
                    this._recvEventArg = recvEventArgs;
                    this.StartRecv(this._recvEventArg);
                    this._status = true;
                    break;
                case ThreadMode.None:
                default:
                    break;
            }
        }

        /// <summary>
        /// 停止接收数据
        /// </summary>
        public void Stop()
        {
            this._status = false;
            var initPara = (UdpClientInitPara)base._initPara;
            if (initPara.ReceiveDataMode == ThreadMode.Sync)
            {
                if (this._receiveThread != null)
                {
                    this._receiveThread.Stop();
                }
            }
        }




        /// <summary>
        /// 发送数据
        /// </summary>
        /// <param name="sendDataOptions">发送的数据选项</param>
        protected override void PrimitiveSend(ISendDataOptions sendDataOptions)
        {
            if (sendDataOptions == null ||
                sendDataOptions.Data == null ||
                sendDataOptions.Data.DataType != BufferDataType.File && sendDataOptions.Data.Length == 0)
            {
                return;
            }

            var udpSendOptions = (UdpSendDataOptions)sendDataOptions;
            BufferData bufferData = udpSendOptions.Data;

            switch (bufferData.DataType)
            {
                case BufferDataType.Bytes:
                    this.PrimitiveSendTo(bufferData.Bytes, (int)bufferData.Offset, (int)bufferData.Length, udpSendOptions.SocketFlags, udpSendOptions.DstEndPoint);
                    break;
                case BufferDataType.Stream:
                    this.SendTo(bufferData.Stream, bufferData.Offset, bufferData.Length, udpSendOptions.SocketFlags, udpSendOptions.DstEndPoint);
                    break;
                case BufferDataType.File:
                    using (FileStream fs = File.OpenRead(bufferData.FilePath))
                    {
                        bufferData.UpdateFileBufferLength(fs.Length);
                        this.SendTo(fs, bufferData.Offset, bufferData.Length, udpSendOptions.SocketFlags, udpSendOptions.DstEndPoint);
                    }
                    break;
#if CORE
                case BufferDataType.Memory:
                    if (bufferData.Offset != 0 || bufferData.Length != bufferData.MemoryBuffer.Length)
                    {
                        Memory<byte> memory2 = bufferData.MemoryBuffer.Slice((int)bufferData.Offset, (int)bufferData.Length);
                        this.SendTo(memory2, udpSendOptions.SocketFlags, udpSendOptions.DstEndPoint);
                    }
                    else
                    {
                        this.SendTo(bufferData.MemoryBuffer, udpSendOptions.SocketFlags, udpSendOptions.DstEndPoint);
                    }
                    break;
#endif
                case BufferDataType.BinaryReader:
                    this.SendTo(bufferData.Reader.BaseStream, bufferData.Offset, bufferData.Length, udpSendOptions.SocketFlags, udpSendOptions.DstEndPoint);
                    break;
                case BufferDataType.IntPtr:
                    if (bufferData.Length > ArrayEx.MaxLength)
                    {
                        throw new ArgumentOutOfRangeException($"udp发送数据长度不能超过{ArrayEx.MaxLength}");
                    }

                    byte[] buffer = new byte[bufferData.Length];
                    Marshal.Copy(bufferData.Ptr + (int)bufferData.Offset, buffer, 0, buffer.Length);
                    this.PrimitiveSendTo(buffer, 0, buffer.Length, udpSendOptions.SocketFlags, udpSendOptions.DstEndPoint);
                    break;
                default:
                    throw new NotImplementedException($"{nameof(NetUdpClient)}发送数据,未实现的缓存数据类型{bufferData.DataType.ToString()}");
            }

        }


        /// <summary>
        /// 发送数据,返回已发送数据长度
        /// </summary>
        /// <param name="buffer">要发送的数据</param>
        /// <param name="offset">发送数据起始位置</param>
        /// <param name="size">发送数据大小</param>
        /// <param name="socketFlags">SocketFlags</param>
        /// <param name="dstEndPoint">目的地EndPoint</param>
        /// <exception cref="ObjectDisposedException"></exception>
        /// <returns>返回已发送数据长度</returns>
        private int PrimitiveSendTo(byte[] buffer, int offset, int size, SocketFlags socketFlags, EndPoint dstEndPoint)
        {
            if (this._sendInterval100NS > 0)
            {
                lock (this._sendIntervalLock)
                {
                    if (this.WaitSendInterval())
                    {
                        if (this._disposed)
                        {
                            throw new ObjectDisposedException(this.ToString());
                        }
                    }

                    return this._client.SendTo(buffer, offset, size, socketFlags, dstEndPoint);
                }
            }
            else
            {
                return this._client.SendTo(buffer, offset, size, socketFlags, dstEndPoint);
            }
        }

        /// <summary>
        /// 发送数据,返回已发送数据长度
        /// </summary>
        /// <param name="buffer">要发送的数据</param>
        /// <param name="dstEndPoint">目的地EndPoint</param>
        /// <returns>返回已发送数据长度</returns>
        public int SendTo(byte[] buffer, EndPoint dstEndPoint)
        {
            return this.PrimitiveSendTo(buffer, 0, buffer.Length, SocketFlags.None, dstEndPoint);
        }

        /// <summary>
        /// 发送数据,返回已发送数据长度
        /// </summary>
        /// <param name="buffer">要发送的数据</param>
        /// <param name="offset">发送数据起始位置</param>
        /// <param name="size">发送数据大小</param>
        /// <param name="socketFlags">SocketFlags</param>
        /// <param name="dstEndPoint">目的地EndPoint</param>
        /// <exception cref="ObjectDisposedException"></exception>
        /// <returns>返回已发送数据长度</returns>
        public int SendTo(byte[] buffer, int offset, int size, SocketFlags socketFlags, EndPoint dstEndPoint)
        {
            if (buffer == null)
            {
                throw new ArgumentNullException(nameof(buffer));
            }

            if (dstEndPoint == null)
            {
                throw new ArgumentNullException(nameof(dstEndPoint));
            }

            if (this._disposed)
            {
                throw new ObjectDisposedException(this.ToString());
            }

            return this.PrimitiveSendTo(buffer, offset, size, socketFlags, dstEndPoint);
        }

        /// <summary>
        /// 发送数据
        /// </summary>
        /// <param name="stream">Stream</param>
        /// <param name="offset">数据中偏移位置,小于等于0表示从0开始</param>
        /// <param name="length">要发送的数据长度,小于等于0表示发送到数据结尾</param>
        /// <param name="socketFlags">SocketFlags</param>
        /// <param name="dstEndPoint">目的地EndPoint</param>
        /// <returns>返回已发送数据长度</returns>
        public int SendTo(Stream stream, long offset, long length, SocketFlags socketFlags, EndPoint dstEndPoint)
        {
            if (stream == null)
            {
                throw new ArgumentNullException(nameof(stream));
            }

            if (dstEndPoint == null)
            {
                throw new ArgumentNullException(nameof(dstEndPoint));
            }

            if (offset < 0)
            {
                offset = 0;
            }
            else if (offset >= stream.Length)
            {
                throw new ArgumentOutOfRangeException(nameof(offset));
            }

            if (length < 0)
            {
                length = stream.Length - offset;
            }
            else
            {
                if (length > ArrayEx.MaxLength)
                {
                    throw new ArgumentOutOfRangeException($"udp发送数据长度不能超过{ArrayEx.MaxLength}");
                }

                if (offset + length > stream.Length)
                {
                    throw new ArgumentOutOfRangeException(nameof(length));
                }
            }

            byte[] buffer = new byte[length];
            stream.Seek(offset, SeekOrigin.Begin);
            stream.Read(buffer, 0, buffer.Length);
            return this.PrimitiveSendTo(buffer, 0, buffer.Length, socketFlags, dstEndPoint);
        }

#if CORE
        /// <summary>
        /// 发送数据
        /// </summary>
        /// <param name="buffer">数据</param>
        /// <param name="socketFlags">SocketFlags</param>
        /// <param name="dstEndPoint">目的地EndPoint</param>
        /// <returns>返回已发送数据长度</returns>
        public int SendTo(Memory<byte> buffer, SocketFlags socketFlags, EndPoint dstEndPoint)
        {
            if (dstEndPoint == null)
            {
                throw new ArgumentNullException(nameof(dstEndPoint));
            }

            if (this._sendInterval100NS > 0)
            {
                lock (this._sendIntervalLock)
                {
                    if (this.WaitSendInterval())
                    {
                        if (this._disposed)
                        {
                            throw new ObjectDisposedException(this.ToString());
                        }
                    }

                    return this._client.SendTo(buffer.Span, socketFlags, dstEndPoint);
                }
            }
            else
            {
                return this._client.SendTo(buffer.Span, socketFlags, dstEndPoint);
            }
        }
#endif


        private SocketAsyncEventArgs _sendEventArg = null;
        /// <summary>
        /// 异步发送数据
        /// </summary>
        /// <param name="dstEndPoint"></param>
        /// <param name="buffer"></param>
        /// <param name="offset"></param>
        /// <param name="size"></param>
        /// <param name="socketFlags"></param>
        /// <exception cref="ArgumentNullException"></exception>
        /// <exception cref="ArgumentOutOfRangeException"></exception>
        public void SendToAsync(EndPoint dstEndPoint, byte[] buffer, int offset = 0, int size = -1, SocketFlags socketFlags = SocketFlags.None)
        {
            if (buffer == null)
            {
                throw new ArgumentNullException(nameof(buffer));
            }

            if (dstEndPoint == null)
            {
                throw new ArgumentNullException(nameof(dstEndPoint));
            }

            if (offset < 0)
            {
                offset = 0;
            }
            else if (offset >= buffer.Length)
            {
                throw new ArgumentOutOfRangeException("发送起始位置超出buffer范围");
            }

            if (size < 0)
            {
                size = buffer.Length - offset;
            }

            if (offset + size > buffer.Length)
            {
                throw new ArgumentOutOfRangeException("发送数据大小超出buffer范围");
            }

            if (this._sendInterval100NS > 0)
            {
                lock (this._sendIntervalLock)
                {
                    if (this.WaitSendInterval())
                    {
                        if (this._disposed)
                        {
                            throw new ObjectDisposedException(this.ToString());
                        }
                    }

                    this.PrimitiveSendToAsync(dstEndPoint, buffer, offset, size, socketFlags);
                }
            }
            else
            {
                this.PrimitiveSendToAsync(dstEndPoint, buffer, offset, size, socketFlags);
            }
        }

        private void PrimitiveSendToAsync(EndPoint dstEndPoint, byte[] buffer, int offset, int size, SocketFlags socketFlags)
        {
            if (this._sendEventArg == null)
            {
                this._sendEventArg = new SocketAsyncEventArgs();
                this._sendEventArg.Completed += _sendEventArg_Completed;
            }
            this._sendEventArg.SetBuffer(buffer, offset, size);
            this._sendEventArg.RemoteEndPoint = dstEndPoint;
            this._client.SendToAsync(this._sendEventArg);
        }

        private void _sendEventArg_Completed(object sender, SocketAsyncEventArgs e)
        {
            if (e.SocketError != SocketError.Success)
            {
                ZLoger.Warn($"通过UDP发送数据到\"{NetHelper.GetIPPortString(e.RemoteEndPoint)}\"失败");
            }
        }



        /// <summary>
        /// 等待发送间隔[返回true,有等待发送间隔;返回false无等待]
        /// </summary>
        /// <returns>返回true,有等待发送间隔;返回false无等待</returns>
        private bool WaitSendInterval()
        {
            var mod100NS = _sendDataIntervalTimestampFactory.GetTimestamp() - this._lastSendDataTimestamp100NS;
            if (mod100NS < this._sendInterval100NS)
            {
                //两次之间的发送间隔小于了最小值，则等待差值毫秒
                var ts = TimeSpan.FromMilliseconds(((double)(this._sendInterval100NS - mod100NS)) / 10000);
                if (this._sendDataIntervalToken.WaitOne(ts))
                {
                    //资源已释放,取消发送
                    return true;
                }
            }

            return false;
        }



        /// <summary>
        /// Dispose
        /// </summary>
        /// <param name="disposing"></param>
        protected override void Dispose(bool disposing)
        {
            try
            {
                if (this._receiveThread != null)
                {
                    this._receiveThread.Stop();
                    this._receiveThread.Dispose();
                }

                this._client.Close();
                this._client.Dispose();

                if (this._recvEventArg != null)
                {
                    this._recvEventArg.Dispose();
                }

                if (this._sendEventArg != null)
                {
                    this._sendEventArg.Dispose();
                }

                if (this._sendDataIntervalToken != null)
                {
                    this._sendDataIntervalToken.Dispose();
                }
            }
            catch (Exception ex)
            {
                ZLoger.Error(ex, "释放资源异常");
            }
        }
    }
}
